{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Blue Team KubeHound Workflow\n",
    "\n",
    "A step by step example workflow of how to use KubeHound for an incident response scenario."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Initial Setup\n",
    "\n",
    "Connection is being initated directly from the docker using the env vars `GRAPH_NOTEBOOK_HOST` and `GRAPH_NOTEBOOK_PORT`. To overwrite it you can use the magic `%%graph_notebook_config` [details here](https://github.com/aws/graph-notebook/tree/main/additional-databases/gremlin-server#connecting-to-a-local-gremlin-server-from-jupyter).\n",
    "\n",
    "Now set the appearance customizations for the notebook. You can see a guide on possible options [here](https://github.com/aws/graph-notebook/blob/623d43827f798c33125219e8f45ad1b6e5b29513/src/graph_notebook/notebooks/01-Neptune-Database/02-Visualization/Grouping-and-Appearance-Customization-Gremlin.ipynb#L680)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "init_cell": true
   },
   "outputs": [],
   "source": [
    "%%capture \"Remove this line to see debug information\"\n",
    "%%graph_notebook_vis_options\n",
    "{\n",
    "  \"edges\": {\n",
    "    \"smooth\": {\n",
    "      \"enabled\": true,\n",
    "      \"type\": \"dynamic\"\n",
    "    },\n",
    "    \"arrows\": {\n",
    "      \"to\": {\n",
    "        \"enabled\": true,\n",
    "        \"type\": \"arrow\"\n",
    "      }\n",
    "    }\n",
    "  }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Workflow"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Compromised Credentials\n",
    "\n",
    "Let us consider a scenario where a user's credentials have been compromised. We can use KubeHound to identify the resources that the user has access to, whether any lead to critical assets and what attacks might have been leveraged.\n",
    "\n",
    "First let's see whether there are any critical paths accessible. Because Kubernetes delegates the management of users' group memberships to third party components (e.g identity providers), we need to check paths from both the user and any groups they are a member of. \n",
    "\n",
    "**NOTE** the mapping of users to groups must be done prior to this step as it falls outside the scope of KubeHound and is specific to the identity provider."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin\n",
    "kh.identities()\n",
    "    .has(\"runID\", graph.variables().get('runID_yourid').get())\n",
    "    .or(\n",
    "        has(\"type\", \"Group\").has(\"name\", within(\"dept-sales\", \"k8s-users\")),\n",
    "        has(\"type\", \"User\").has(\"name\", \"bits.barkley@datadoghq.com\"))\n",
    "    .hasCriticalPath()\n",
    "    .values(\"name\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's examine the possible attack paths that could be taken by an attacker who has access to the compromised credentials."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "%%gremlin -d class -g critical -le 50 -p inv,oute\n",
    "kh.identities()\n",
    "    .has(\"runID\", graph.variables().get('runID_yourid').get())\n",
    "    .or(\n",
    "        has(\"type\", \"Group\").has(\"name\", within(\"dept-sales\", \"k8s-users\")),\n",
    "        has(\"type\", \"User\").has(\"name\", \"bits.barkley@datadoghq.com\"))\n",
    "    .criticalPaths()\n",
    "    .by(elementMap())\n",
    "    .limit(100)  // Limit the number of results for large clusters"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Skip to the [next section](#advanced-workflows) to for more in-depth workflows to surface potential detection sources and eliminate attacks to narrow down the scope."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Compromised Container\n",
    "\n",
    "Consider the scenario where a container has been compromised via a malicious dependency found within a known set of images. We can use KubeHound to identify the resources that the container has access to, whether any lead to critical assets and what attacks might have been leveraged.\n",
    "\n",
    "First let's see whether there are any critical paths accessible"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin\n",
    "kh.containers()\n",
    "    .has(\"runID\", graph.variables().get('runID_yourid').get())\n",
    "    .or(\n",
    "        has(\"image\", TextP.containing(\"nginx\")),    // Replace with your image name\n",
    "        has(\"image\", TextP.containing(\"cilium\")))   // Replace with your image name\n",
    "    .hasCriticalPath()\n",
    "    .values(\"name\")\n",
    "    .dedup()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's examine the possible attack paths that could be taken by the attacker."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -d class -g critical -le 50 -p inv,oute\n",
    "kh.containers()\n",
    "    .has(\"runID\", graph.variables().get('runID_yourid').get())\n",
    "    .or(\n",
    "        has(\"image\", TextP.containing(\"nginx\")),    // Replace with your image name\n",
    "        has(\"image\", TextP.containing(\"cilium\")))   // Replace with your image name\n",
    "    .criticalPaths()\n",
    "    .by(elementMap())\n",
    "    .limit(100)  // Limit the number of results for large clusters"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Advanced Workflows"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In a real-world deployment the above queries may throw up too many results to be actionable. In such cases we can use KubeHound to narrow down the scope of the investigation.\n",
    "\n",
    "### Focus on container escapes\n",
    "\n",
    "For example in the compromised container case we may wish to understand the easiest attack path that the attacker could have taken and focus our detections efforts there. Let's first look for any potential container escapes from the compromised container. These provide easy privilege escalation for an attacker but could also provide detection opportunities for us. The query below provides a list of container escapes possible from the matching images."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin\n",
    "kh.containers()\n",
    "    .has(\"runID\", graph.variables().get('runID_yourid').get())\n",
    "    .where(out().hasLabel(\"Node\"))\n",
    "    .or(\n",
    "        has(\"image\", TextP.containing(\"nginx\")),    // Replace with your image name\n",
    "        has(\"image\", TextP.containing(\"cilium\"))    // Replace with your image name\n",
    "    )  \n",
    "    .project('image',\"escapes\")\n",
    "    .by(values(\"image\"))\n",
    "    .by(outE().where(inV().hasLabel(\"Node\")).label().fold())\n",
    "    .dedup()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Shortest attack paths\n",
    "\n",
    "Attackers are incentivized to take the shortest path to their target. We can use KubeHound to identify the shortest attack paths from the compromised container to the critical assets. This can help us focus our detection efforts on the most likely attack paths.\n",
    "\n",
    "First we calculate the length of the shortest attack path from our target container to a critical asset:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin \n",
    "kh.containers()\n",
    "    .has(\"runID\", graph.variables().get('runID_yourid').get())\n",
    "    .or(\n",
    "        has(\"image\", TextP.containing(\"nginx\")),    // Replace with your image name\n",
    "        has(\"image\", TextP.containing(\"cilium\")))   // Replace with your image name\n",
    "    .minHopsToCritical()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then we can find the unique attack paths of that length:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -d class -g critical -le 50 -p inv,oute\n",
    "kh.containers()\n",
    "    .has(\"runID\", graph.variables().get('runID_yourid').get())\n",
    "    .or(\n",
    "        has(\"image\", TextP.containing(\"nginx\")),    // Replace with your image name\n",
    "        has(\"image\", TextP.containing(\"cilium\")))   // Replace with your image name\n",
    "    .repeat(\n",
    "      outE().inV().simplePath())\n",
    "    .emit()\n",
    "    .until(\n",
    "        has(\"critical\", true)\n",
    "        .or().loops().is(4))\n",
    "    .has(\"critical\", true)\n",
    "    .dedup()\n",
    "    .path()\n",
    "    .by(elementMap())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Blast radius evaluation\n",
    "\n",
    "It may be the case that the compromised container does not have a path to a critical asset (at least within the KubeHound model). In this case in can be useful to understand the blast radius of the compromised container. We can use KubeHound to identify all the resources that an attacker could have accessed from a compromised container."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%gremlin -d name -g class -le 50 -p inv,oute\n",
    "kh.containers()\n",
    "    .has(\"runID\", graph.variables().get('runID_yourid').get())\n",
    "    .or(\n",
    "        has(\"image\", TextP.containing(\"nginx\")),\n",
    "        has(\"image\", TextP.containing(\"cilium\")))\n",
    "   \t.repeat(\n",
    "      outE().inV().simplePath())\n",
    "    .times(5) // Increase to expand the potential blast radius, but graph size will increase exponentially!\n",
    "    .emit()\n",
    "    .path()\n",
    "    .by(elementMap())\n",
    "    .limit(100)  // Limit the number of results for large clusters"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
